﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using AmCharts;
using AmCharts.Windows;
using AmCharts.Windows.Column;
using Microsoft.Research.DynamicDataDisplay;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Microsoft.Research.DynamicDataDisplay.PointMarkers;
using Microsoft.Research.DynamicDataDisplay.Charts;
using Microsoft.Research.DynamicDataDisplay.ViewportRestrictions;
using Microsoft.Research.DynamicDataDisplay.Common;
using Microsoft.Research.DynamicDataDisplay.Navigation;
using AmCharts.Windows.Core;
using Visualization.DataModel;

namespace Visualization
{
    /// <summary>
    /// Interaction logic for Window.xaml
    /// </summary>
    public partial class DensityGraph : Window
    {
        Dictionary<int, SFPStep> SFPSteps;
        LineGraph xGraph, yGraph, thetaGraph, xsGraph, ysGraph, tsGraph;
                
        public DensityGraph(System.Windows.Forms.Screen screen){
            InitializeComponent();            
            this.Show();
            this.WindowState = System.Windows.WindowState.Normal;
            this.Left = screen.Bounds.Left;
            this.Top = screen.Bounds.Top;            
            this.WindowState = System.Windows.WindowState.Maximized;
        }

        private void setupInterval(){
            timeSlider.Minimum = 1;
            timeSlider.Maximum = SFPSteps.Count;
            timeSlider.Ticks = generateTicks();
            timeSlider.SmallChange = 1;
            timeSlider.LargeChange = 5;
        }

        private DoubleCollection generateTicks(){
            DoubleCollection temp = new DoubleCollection();
            foreach (int step in SFPSteps.Keys)
                temp.Add((double)step);
            return temp;
        }

        Func<Point, double> mapping = delegate(Point p) { return p.X; };
        Func<Point, double> pMapping = delegate(Point p) { return p.Y; };        

        private void setupCharts(){   
            XY_W_Y_Chart.XMemberPath = "X";
            XY_W_Y_Chart.YMemberPath = "Y";
            XY_W_Y_Chart.ValueMemberPath = "Probability";
            EnumerableDataSource<Point> dsX = new EnumerableDataSource<Point>(createHistogram(SFPSteps[20],GraphIndependentVar.X));
            EnumerableDataSource<Point> dsY = new EnumerableDataSource<Point>(createHistogram(SFPSteps[20], GraphIndependentVar.Y));
            EnumerableDataSource<Point> dsTheta = new EnumerableDataSource<Point>(createHistogram(SFPSteps[20], GraphIndependentVar.Theta));
            dsX.SetXMapping(mapping);
            dsX.SetYMapping(pMapping);
            dsY.SetXMapping(mapping);
            dsY.SetYMapping(mapping);
            dsY.SetXMapping(mapping);
            dsY.SetYMapping(pMapping);
            xGraph = XVsP.AddLineGraph(dsX, new Pen(Brushes.Blue, 3), new PenDescription("X Vs Probability"));
            XVsP.Viewport.FitToView();
            yGraph = YVsP.AddLineGraph(dsY, new Pen(Brushes.Red, 3), new PenDescription("Y Vs Probability"));
            YVsP.Viewport.FitToView();
            thetaGraph = ThetaVsP.AddLineGraph(dsTheta, new Pen(Brushes.Green, 3), new PenDescription("Theta Vs Probability"));
            ThetaVsP.Viewport.FitToView();
        }
            
        private void timeChanged(object sender, RoutedPropertyChangedEventArgs<double> e){
            XY_W_Y_Chart.DataItemsSource = SFPSteps[(int)timeSlider.Value + 1].particles;
            timeLabel.Content = "Time: " + timeSlider.Value * 25 + " ms";
            XVsP.Children.RemoveAll(typeof(LineGraph));
            YVsP.Children.RemoveAll(typeof(LineGraph));
            ThetaVsP.Children.RemoveAll(typeof(LineGraph));   
            xGraph = XVsP.AddLineGraph(new EnumerableDataSource<Point>(createHistogram(SFPSteps[(int)timeSlider.Value + 1], GraphIndependentVar.X)){
                XMapping = mapping,
                YMapping = pMapping
            }, new Pen(Brushes.Blue, 1), new PenDescription("X Vs Probability"));
            yGraph = YVsP.AddLineGraph(new EnumerableDataSource<Point>(createHistogram(SFPSteps[(int)timeSlider.Value + 1], GraphIndependentVar.Y)){
                XMapping = mapping,
                YMapping = pMapping
            }, new Pen(Brushes.Red, 1), new PenDescription("Y Vs Probability"));
            double xScale = YVsP.Visible.Width / XVsP.Visible.Width;
            YVsP.Visible = new DataRect(YVsP.Visible.XMin, YVsP.Visible.YMin, YVsP.Visible.Width * xScale, YVsP.Visible.Height);
            YVsP.FitToView();
            thetaGraph = ThetaVsP.AddLineGraph(new EnumerableDataSource<Point>(createHistogram(SFPSteps[(int)timeSlider.Value + 1], GraphIndependentVar.Theta)){
                XMapping = mapping,
                YMapping = pMapping
            }, new Pen(Brushes.Green, 1), new PenDescription("Theta Vs Probability"));
            ThetaVsP.FitToView();
            double xIndVal = (SFPSteps[(int)timeSlider.Value + 1].HighestProbabilityParticle.X - SFPSteps[(int)timeSlider.Value + 1].xBar) / SFPSteps[(int)timeSlider.Value + 1].xSigma;
            double yIndVal = (SFPSteps[(int)timeSlider.Value + 1].HighestProbabilityParticle.Y - SFPSteps[(int)timeSlider.Value + 1].yBar) / SFPSteps[(int)timeSlider.Value + 1].ySigma;
            double thetaIndVal = (SFPSteps[(int)timeSlider.Value + 1].HighestProbabilityParticle.Theta - SFPSteps[(int)timeSlider.Value + 1].thetaBar) / SFPSteps[(int)timeSlider.Value + 1].thetaSigma;
            List<Point> sfpxPoint = new List<Point>();
            sfpxPoint.Add(new Point(xIndVal, 0));
            sfpxPoint.Add(new Point(xIndVal, 1));
            List<Point> sfpyPoint = new List<Point>();
            sfpyPoint.Add(new Point(yIndVal, 0));
            sfpyPoint.Add(new Point(yIndVal, 1));
            List<Point> sfpthetaPoint = new List<Point>();
            sfpthetaPoint.Add(new Point(thetaIndVal, 0));
            sfpthetaPoint.Add(new Point(thetaIndVal, 1));
            xsGraph = XVsP.AddLineGraph(new EnumerableDataSource<Point>(sfpxPoint) { XMapping = mapping, YMapping = pMapping }, new Pen(Brushes.Orange, 1), new PenDescription("SFP Solution"));
            ysGraph = YVsP.AddLineGraph(new EnumerableDataSource<Point>(sfpyPoint) { XMapping = mapping, YMapping = pMapping }, new Pen(Brushes.Orange, 1), new PenDescription("SFP Solution"));
            tsGraph = ThetaVsP.AddLineGraph(new EnumerableDataSource<Point>(sfpthetaPoint) { XMapping = mapping, YMapping = pMapping }, new Pen(Brushes.Orange, 1), new PenDescription("SFP Solution"));
        }

        public enum GraphIndependentVar : sbyte{
            X = 1,
            Y = 2,
            Theta = 3
        }

        public void createGraph(Dictionary<int, SFPStep> steps){
            SFPSteps = steps;
            setupInterval();
            setupCharts();
            this.Visibility = Visibility.Visible;
            timeSlider.Value = 4;
            XY_With_Yaw.Refresh();
        }

        Func<Point, double> selectData = delegate(Point p) { return p.X; };
        Func<Point, double> selectMax = delegate(Point p) { return p.Y; };
        
        public List<Point> createHistogram(SFPStep step, GraphIndependentVar var){
            List<Particle> ptcls = step.particles;
            List<Point> rawData = new List<Point>();
            List<Point> finalData = new List<Point>();
            Dictionary<Point, double> bins = new Dictionary<Point, double>();
            List<bool> pointsAdded = new List<bool>();
            Label outlierLabel;
            double min, max, binSize, mean, sigma, numDevs, leftCount = 0, rightCount = 0;
            numDevs = 3;
            binSize = 20;
            //select data to use - (x, y, theta)
            #region Data Selection
            if (var.Equals(GraphIndependentVar.X)){
                foreach (Particle p in ptcls)
                    rawData.Add(new Point(p.X, p.Probability));
                mean = step.xBar;
                sigma = step.xSigma;
                outlierLabel = xStdDevText;
            }
            else if (var.Equals(GraphIndependentVar.Y)){
                foreach (Particle p in ptcls)
                    rawData.Add(new Point(p.Y, p.Probability));
                mean = step.yBar;
                sigma = step.ySigma;
                outlierLabel = yStdDevText;
            }
            else{
                foreach (Particle p in ptcls)
                    rawData.Add(new Point(p.Theta, p.Probability));
                mean = step.thetaBar;
                sigma = step.thetaSigma;
                outlierLabel = thetaStdDevText;
            }
            #endregion

            //calculate bins
            #region Bin Calculation
            min = mean - (sigma * numDevs);
            max = mean + (sigma * numDevs);
            binSize = sigma / binSize;
            Point leftOutlier = new Point(((min - binSize) - mean) / sigma, (min - mean) / sigma);
            bins.Add(leftOutlier, 0);
            pointsAdded.Add(false);
            for (double i = min; i < max; i += binSize){
                bins.Add(new Point((i - mean) / sigma, (i + binSize - mean) / sigma), 0);
                pointsAdded.Add(false);
            }
            //outlier bins
            Point rightOutlier = new Point((max + mean) / sigma, (max + binSize - mean) / sigma);
            bins.Add(rightOutlier, 0);
            pointsAdded.Add(false);
            #endregion

            //create frequency table and accumulate probability
            #region Probability Accumulation
            foreach (Point p in rawData){
                if (p.X >= min && p.X < max)
                    for (int i = 0; i < bins.Count; i++){
                        Point temp = bins.Keys.ElementAt(i);
                        if (((p.X - mean) / sigma) >= temp.X && ((p.X - mean) / sigma) < temp.Y){
                            bins[temp] += p.Y;
                            pointsAdded[i] = true;
                        }
                    }
                else if (p.X < min){
                    //bins[leftOutlier] += p.Y;
                    leftCount++;
                    //pointsAdded[0] = true;
                }
                else if (p.X > max){
                    //bins[rightOutlier] += p.Y;
                    rightCount++;
                    //pointsAdded[bins.Count - 1] = true;
                }
            }
            #endregion

            //convert to a list of points that at least have one raw data point added to them and add outliers
            #region Data Finalization
            for (int i = 0; i < bins.Count; i++){
                Point pnt = new Point(bins.Keys.ElementAt(i).X, bins.Values.ElementAt(i));
                if(pointsAdded[i])
                    finalData.Add(pnt);
            }
            #endregion

            //normalize probability
            #region Probability Normalization
            try{
                double maxProb = finalData.Max(selectMax);
                for (int i = 0; i < finalData.Count; i++){
                    Point temp = finalData[i];
                    temp.Y = temp.Y / maxProb;
                    finalData[i] = temp;
                }
            }
            catch (Exception err) { }
            #endregion
            outlierLabel.Content = "Left Outliers: " + leftCount + /*" Value: " + bins[leftOutlier] + */" | Right Outliers: " + rightCount /*+ " Value: " + bins[rightOutlier]*/;
            return finalData;
        }
    }
}
